/*
 *  Copyright 2012 yura.
 * 
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 * 
 *       http://www.apache.org/licenses/LICENSE-2.0
 * 
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *  under the License.
 */

package org.opu.odg.dg.impl;

import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.opu.odg.dg.DocumentGeneraionService;
import org.opu.odg.dg.GenerationSettings;
import org.opu.odg.dg.incomingdata.ContentDescription;
import org.opu.odg.dg.incomingdata.CustomContentDescription;
import org.opu.odg.dg.logicbeans.Template;
import org.opu.odg.dg.xml.ContentEndElement;
import org.opu.odg.dg.xml.ContentMapping;
import org.opu.odg.dg.xml.ContentMapping.ContentEntry;
import org.opu.odg.dg.xml.ContentStartElement;
import org.opu.odg.dg.xml.Element;
import org.opu.odg.dg.xml.TextMarkupUtil.Lexem;
import org.opu.odg.dg.xml.TextMarkupUtil.LexemType;
import org.opu.odg.dg.xml.XmlCharsetUtils;
import org.opu.odg.dg.xml.XmlParser;
import org.opu.odg.dg.xml.XmlTag;
import org.xml.sax.SAXException;

/**
 *
 * @author yura
 */
public class DocumentGeneraionServiceImpl implements DocumentGeneraionService{

    private DocumentGenerationContext context;

    public DocumentGeneraionServiceImpl(DocumentGenerationContext context) {
        this.context = context;
    }

    @Override
    public void generateDocument(GenerationSettings settings, OutputStream out)
            throws Exception {
        // 1st step is read template
        Template template = context.getTemplateRepository().get(settings.getTemplateId());
        Map<String, String> filesForChanging = template.getTemplateFileContent();
        Map<String, String> resultMap = new HashMap<String, String>();
        Map<String, ContentDescription> contents = collectContents(template, settings);

        TextDocumentGenerator tdg = new TextDocumentGenerator(context, settings, contents);
        try {
            for (Entry<String, String> entry : filesForChanging.entrySet()) {
                String value = entry.getValue();
                ContentMapping mapping = XmlParser.parseDocument(value);
                mapping.validate();

                // 2nd we have mapping, now we have to create plain model
                for (ContentEntry contentEntry : mapping.getMappedEntry()) {
                    processContentEntry(contentEntry, contents);
                }
                
                ArrayList<Lexem> lexems = new ArrayList<Lexem>();
                lexems.add(new Lexem(XmlCharsetUtils.getXmlProlog(value), LexemType.TEXT));
                lexems.addAll(mapping.getRoot().toLexems());
                String generatedDocument = tdg.generateDocument(lexems, value.length() * 3);

                // 3d step process it like a simple text
                resultMap.put(entry.getKey(), generatedDocument);
            }
        } catch (SAXException ex) {
            if (filesForChanging.size() == 1) {
                /*
                 * Try to process it as a plain text.
                 */
                Entry<String, String> entry = filesForChanging.entrySet().iterator().next();
                resultMap.put(entry.getKey(), tdg.generateDocument(entry.getValue()));
            } else {
                throw ex;
            }
        }

        // 4th store data
        template.setResolvedFileContent(resultMap);
        template.getTemplateFile().store(out);
    }

    @Override
    public List<String> validate(Template template, List<String> globalVars,
            List<ContentEntry> mappedEntry) throws Exception {
//        List<ContentEntry> mappedEntry = new ArrayList<ContentEntry>();
//        List<String> globalVars = new ArrayList<String>();

        Map<String, String> filesForChanging = template.getTemplateFileContent();
        for (Entry<String, String> entry : filesForChanging.entrySet()) {
            ContentMapping mapping = XmlParser.parseDocument(entry.getValue());
            mapping.validate();
            mappedEntry.addAll(mapping.getMappedEntry());
            globalVars.addAll(mapping.getVarNameList());
        }

        List<String> ret = new ArrayList<String>();
        for (String string : globalVars) {
            ret.add("$" + string);
        }
        for (ContentEntry contentEntry : mappedEntry) {
            ret.add("#" + contentEntry.name);
        }

        return ret;
    }

    private Map<String, ContentDescription> collectContents(Template template,
            GenerationSettings settings) throws IOException {
        Map<String, ContentDescription> contents = new
                HashMap<String, ContentDescription>(template.getContents());
        Map<String, CustomContentDescription> descriptions = settings.getDescriptions();

        for (String string : descriptions.keySet()) {
            ContentDescription get = contents.get(string);
            CustomContentDescription customContent = descriptions.get(string);
            if (get != null) {
                customContent.setDescribesExternalTag(get.isDescribesExternalTag());
            }

            String[] headers = customContent.getHeaders();
            if ((headers == null || headers.length == 0)
                    && get instanceof CustomContentDescription){
                customContent.setHeaders(((CustomContentDescription)get).getHeaders());
            }
        }

        contents.putAll(descriptions);

        return contents;
    }

    private void processContentEntry(ContentEntry contentEntry,
            Map<String, ContentDescription> contents) {

        ContentDescription content = contents.get(contentEntry.name);
        if (content == null) {
            throw new IllegalArgumentException("The content " + contentEntry.name
                    + " does not exist");
        }

        ArrayList<XmlTag> start = copy(contentEntry.xpathStart.getFullPaht());
        ArrayList<XmlTag> end = copy(contentEntry.getXpathEnd().getFullPaht());

        int i = 0;
        XmlTag commonParent = null;
        while ((commonParent = start.get(i)).equals(end.get(i))) {
            i ++;
        }
        
        commonParent = commonParent.getParent();

        if (content.isDescribesExternalTag()) {
            XmlTag parent = commonParent.getParent();
            List<Element> childrens = parent.getChildrens();
            int index = childrens.indexOf(commonParent);
            childrens.add(index, new ContentStartElement(parent, contentEntry.name));
            childrens.add(index + 2, new ContentEndElement(parent, contentEntry.name));
        } else {
            List<Element> childrens = commonParent.getChildrens();
            childrens.add(childrens.indexOf(start.get(i)),
                    new ContentStartElement(commonParent, contentEntry.name));
            childrens.add(childrens.indexOf(end.get(i)) + 1,
                    new ContentEndElement(commonParent, contentEntry.name));
        }

        for (ContentEntry subContentEntry : contentEntry.subContents) {
            processContentEntry(subContentEntry, contents);
        }
    }

    private <T> ArrayList<T> copy(List<T> list) {
        return new ArrayList<T>(list);
    }

}
